// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <fbl/algorithm.h>
#include <limits>
#include <math.h>
#include <zircon/assert.h>

#include "sine-source.h"

zx_status_t SineSource::Init(float freq, float amp, float duration_secs, uint32_t frame_rate,
                             uint32_t channels, uint32_t active,
                             audio_sample_format_t sample_format) {
  if (!frame_rate)
    return ZX_ERR_INVALID_ARGS;

  if (!channels)
    return ZX_ERR_INVALID_ARGS;

  frame_rate_ = frame_rate;
  channels_ = channels;
  active_ = active;

  frames_to_produce_ = (duration_secs == 0.0)
                           ? std::numeric_limits<uint64_t>::max()
                           : static_cast<uint64_t>(duration_secs * static_cast<float>(frame_rate_));
  sine_scalar_ = (freq * 2.0 * M_PI) / frame_rate_;
  amp_ = fbl::clamp<double>(amp, 0.0, 1.0);

  switch (static_cast<audio_sample_format_t>(sample_format & ~AUDIO_SAMPLE_FORMAT_FLAG_MASK)) {
    case AUDIO_SAMPLE_FORMAT_8BIT:
      return InitInternal<AUDIO_SAMPLE_FORMAT_8BIT>();
    case AUDIO_SAMPLE_FORMAT_16BIT:
      return InitInternal<AUDIO_SAMPLE_FORMAT_16BIT>();
    case AUDIO_SAMPLE_FORMAT_20BIT_IN32:
      return InitInternal<AUDIO_SAMPLE_FORMAT_20BIT_IN32>();
    case AUDIO_SAMPLE_FORMAT_24BIT_IN32:
      return InitInternal<AUDIO_SAMPLE_FORMAT_24BIT_IN32>();
    case AUDIO_SAMPLE_FORMAT_32BIT:
      return InitInternal<AUDIO_SAMPLE_FORMAT_32BIT>();
    default:
      return ZX_ERR_INVALID_ARGS;
  }
}

zx_status_t SineSource::GetFormat(Format* out_format) {
  if (out_format == nullptr)
    return ZX_ERR_INVALID_ARGS;

  out_format->frame_rate = frame_rate_;
  out_format->channels = static_cast<uint16_t>(channels_);
  out_format->sample_format = sample_format_;

  return ZX_OK;
}

zx_status_t SineSource::GetFrames(void* buffer, uint32_t buf_space, uint32_t* out_packed) {
  ZX_DEBUG_ASSERT(get_frames_thunk_ != nullptr);
  return ((*this).*(get_frames_thunk_))(buffer, buf_space, out_packed);
}

namespace {

template <audio_sample_format_t SAMPLE_FORMAT>
struct SampleTraits;

template <>
struct SampleTraits<AUDIO_SAMPLE_FORMAT_8BIT> {
  using SampleType = uint8_t;
  using ComputedType = int8_t;
  static constexpr SampleType SilenceValue = 0;
  static SampleType encode(ComputedType v) {
    return static_cast<ComputedType>(static_cast<SampleType>(v) + 0x80);
  }
};

template <>
struct SampleTraits<AUDIO_SAMPLE_FORMAT_16BIT> {
  using SampleType = int16_t;
  using ComputedType = int16_t;
  static constexpr SampleType SilenceValue = 0;
  static SampleType encode(ComputedType v) { return v; }
};

template <>
struct SampleTraits<AUDIO_SAMPLE_FORMAT_20BIT_IN32> {
  using SampleType = int32_t;
  using ComputedType = int32_t;
  static constexpr SampleType SilenceValue = 0;
  static SampleType encode(ComputedType v) {
    return static_cast<SampleType>(static_cast<uint32_t>(v) & 0xFFFFF000);
  }
};

template <>
struct SampleTraits<AUDIO_SAMPLE_FORMAT_24BIT_IN32> {
  using SampleType = int32_t;
  using ComputedType = int32_t;
  static constexpr SampleType SilenceValue = 0;
  static SampleType encode(ComputedType v) {
    return static_cast<SampleType>(static_cast<uint32_t>(v) & 0xFFFFFF00);
  }
};

template <>
struct SampleTraits<AUDIO_SAMPLE_FORMAT_32BIT> {
  using SampleType = int32_t;
  using ComputedType = int32_t;
  static constexpr SampleType SilenceValue = 0;
  static SampleType encode(ComputedType v) { return v; }
};

}  // namespace

template <audio_sample_format_t SAMPLE_FORMAT>
zx_status_t SineSource::InitInternal() {
  using SampleType = typename SampleTraits<SAMPLE_FORMAT>::SampleType;
  using ComputedType = typename SampleTraits<SAMPLE_FORMAT>::ComputedType;

  sample_format_ = SAMPLE_FORMAT;
  get_frames_thunk_ = &SineSource::GetFramesInternal<SAMPLE_FORMAT>;
  frame_size_ = static_cast<uint32_t>(sizeof(SampleType) * channels_);
  amp_ *= std::numeric_limits<ComputedType>::max() - 1;

  return ZX_OK;
}

template <audio_sample_format_t SAMPLE_FORMAT>
zx_status_t SineSource::GetFramesInternal(void* buffer, uint32_t buf_space, uint32_t* out_packed) {
  using Traits = SampleTraits<SAMPLE_FORMAT>;
  using SampleType = typename SampleTraits<SAMPLE_FORMAT>::SampleType;
  using ComputedType = typename SampleTraits<SAMPLE_FORMAT>::ComputedType;

  if ((buffer == nullptr) || (out_packed == nullptr))
    return ZX_ERR_INVALID_ARGS;

  if (finished())
    return ZX_ERR_BAD_STATE;

  ZX_DEBUG_ASSERT(frames_produced_ < frames_to_produce_);
  uint64_t todo =
      fbl::min<uint64_t>(frames_to_produce_ - frames_produced_, buf_space / frame_size_);
  double pos = sine_scalar_ * static_cast<double>(frames_produced_);
  auto buf = reinterpret_cast<SampleType*>(buffer);

  for (uint64_t i = 0; i < todo; ++i) {
    auto val = static_cast<ComputedType>(amp_ * sin(pos));

    for (uint32_t j = 0; j < channels_; ++j) {
      if (active_ == kAllChannelsActive || active_ == j) {
        *(buf++) = Traits::encode(val);
      } else {
        *(buf++) = Traits::SilenceValue;
      }
    }

    pos += sine_scalar_;
  }

  *out_packed = static_cast<uint32_t>(todo * frame_size_);
  frames_produced_ += todo;

  return ZX_OK;
}
